package service

import (
	"batbat-backend/app/dao"
	"batbat-backend/app/model"
	"batbat-backend/library/sort"
	"context"
	"fmt"
	"github.com/gogf/gf/database/gdb"
	"github.com/gogf/gf/errors/gerror"
	"github.com/gogf/gf/frame/g"
)

// Game 游戏服务
var Game = gameService{}

type gameService struct{}

// GetAllGames 获取所有游戏
func (gs *gameService) GetAllGames(ctx context.Context) (res []*model.GameResp, err error) {
	err = g.DB().Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error {
		var games []*model.Game
		err = dao.Game.Ctx(ctx).TX(tx).Scan(&games)
		if err != nil {
			g.Log().Ctx(ctx).Errorf("查询数据库发生错误: %v", err)
			return gerror.New("获取游戏列表失败")
		}
		res = make([]*model.GameResp, len(games))
		for i := range res {
			res[i], err = gs.getGameHelper(ctx, games[i])
			if err != nil {
				g.Log().Ctx(ctx).Errorf("查询游戏发生错误: %v", err)
				return gerror.New("获取游戏列表失败")
			}
		}
		return nil
	})
	return res, err
}

// GetGame 获取游戏详情, uid为0时代表用户未登陆
func (gs *gameService) GetGame(ctx context.Context, gid uint, uid uint) (game *model.GameApiGetGameResp, err error) {
	err = g.DB().Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error {
		game = new(model.GameApiGetGameResp)
		// 查询游戏基本信息
		err := dao.Game.Ctx(ctx).TX(tx).WherePri(gid).Scan(&game.Game)
		if err != nil {
			g.Log().Ctx(ctx).Errorf("查询数据库发生错误: %v", err)
			return gerror.New("查询游戏详情失败")
		}
		if game.Game == nil {
			return gerror.Newf("游戏[%v]不存在", gid)
		}
		// 获取游戏信息
		gameResp, err := gs.getGameHelper(ctx, game.Game)
		if err != nil {
			g.Log().Ctx(ctx).Errorf("查询游戏发生错误: %v", err)
			return gerror.New("查询游戏详情失败")
		}
		game.GameResp = *gameResp
		// 获取游戏详情
		gameDetail, err := gs.getGameDetailHelper(ctx, game.Game)
		if err != nil {
			g.Log().Ctx(ctx).Errorf("查询游戏详情发生错误: %v", err)
			return gerror.New("查询游戏详情失败")
		}
		game.GameDetail = *gameDetail
		return nil
	})
	if uid != 0 && err == nil {
		// 记录用户查看过该游戏的种类
		Recommend.Record(ctx, uid, game.Categories)
	}
	return game, err
}

// getGameHelper 获取游戏基础信息辅助方法
func (gs *gameService) getGameHelper(ctx context.Context, game *model.Game) (resp *model.GameResp, err error) {
	if game == nil || game.Id == 0 {
		return nil, gerror.Newf("无效的游戏数据: %v", game)
	}
	err = g.DB().Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error {
		resp = &model.GameResp{
			Game:       game,
			Developers: make([]*model.Developer, 0),
			Categories: make([]*model.Category, 0),
			IsFavor:    false, // 收藏游戏默认为false
		}
		// 获取游戏开发者
		err := dao.Developer.Ctx(ctx).TX(tx).
			WhereIn(dao.Developer.Columns.Id,
				dao.GameDeveloper.Ctx(ctx).TX(tx).Distinct().
					Fields(dao.GameDeveloper.Columns.Did).
					Where(dao.GameDeveloper.Columns.Gid, game.Id)).
			Scan(&resp.Developers)
		if err != nil {
			return err
		}
		// 获取游戏种类
		err = dao.Category.Ctx(ctx).TX(tx).
			WhereIn(dao.Category.Columns.Id,
				dao.GameCategory.Ctx(ctx).TX(tx).Distinct().
					Fields(dao.GameCategory.Columns.Cid).
					Where(dao.GameCategory.Columns.Gid, game.Id)).
			Scan(&resp.Categories)
		if err != nil {
			return err
		}
		// 图标详细信息
		err = dao.GameIcon.Ctx(ctx).TX(tx).Where(dao.GameIcon.Columns.Gid, game.Id).Scan(&resp.Icon)
		if err != nil {
			return err
		}
		// 游戏状态信息
		err = dao.GameStat.Ctx(ctx).TX(tx).Where(dao.GameStat.Columns.Gid, game.Id).Scan(&resp.Stat)
		if err != nil {
			return err
		}
		// 获取游戏收藏量
		ifavor, err := dao.GameFavor.Ctx(ctx).TX(tx).
			Where(dao.GameFavor.Columns.Gid, game.Id).WhereNull(dao.GameFavor.Columns.DeleteTime).Count()
		if err != nil {
			return err
		}
		resp.Stat.Favor += uint(ifavor)
		// 获取游戏横幅图
		err = dao.GameBanner.Ctx(ctx).TX(tx).Where(dao.GameBanner.Columns.Gid, game.Id).Scan(&resp.Banner)
		if err != nil {
			return err
		}
		// 计算游戏平均分
		record, err := dao.GameScore.Ctx(ctx).TX(tx).
			Fields(dao.GameScore.Columns.Score).
			Where(dao.GameScore.Columns.Gid, game.Id).
			One()
		if err != nil {
			return err
		}
		if score := record.Map()[dao.GameScore.Columns.Score]; score != nil {
			resp.Score = fmt.Sprintf("%.1f", score)
		} else {
			resp.Score = "-.-"
		}
		// 用户是否收藏该游戏
		if user, ok := ctx.Value(model.UserContextKey).(*model.UserContext); ok {
			count, err := dao.GameFavor.Ctx(ctx).TX(tx).
				Where(dao.GameFavor.Columns.Uid, user.Id).
				Where(dao.GameFavor.Columns.Gid, game.Id).
				WhereNull(dao.GameFavor.Columns.DeleteTime).
				Count()
			if err != nil {
				return err
			}
			if count > 0 {
				resp.IsFavor = true
			}
		}
		return nil
	})
	return resp, err
}

// getGameDetailHelper 获取游戏详情辅助方法
func (gs *gameService) getGameDetailHelper(ctx context.Context, game *model.Game) (
	detail *model.GameDetail, err error) {
	// 判断传入参数是否有效
	if game == nil || game.Id == 0 {
		return nil, gerror.Newf("无效的游戏数据: %v", game)
	}
	err = g.DB().Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error {
		detail = new(model.GameDetail)
		// 安装包信息
		err = dao.GameApk.Ctx(ctx).TX(tx).Where(dao.GameApk.Columns.Gid, game.Id).Scan(&detail.Apk)
		if err != nil {
			return err
		}
		// 展示截图
		return dao.GameScreenshot.Ctx(ctx).TX(tx).
			Where(dao.GameScreenshot.Columns.Gid, game.Id).
			Order(dao.GameScreenshot.Columns.Order).
			Scan(&detail.Screenshots)
	})
	return detail, err
}

// Rank 获取游戏排行榜中的游戏
func (gs *gameService) Rank(ctx context.Context, pn int, limit int) (resp *model.GameApiRankResp, err error) {
	resp = &model.GameApiRankResp{
		Page: model.Page{
			Prev:  pn - 1,
			Count: 150,
			Limit: limit,
		},
	}
	if (pn-1)*limit >= resp.Count {
		return nil, gerror.New("获取排行榜失败, 请检查参数`pn`或`limit`")
	}
	// 计算下一页的页码
	if pn*limit < resp.Count {
		resp.Next = pn + 1
	} else {
		resp.Next = 0
	}
	err = g.DB().Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error {
		// 获取排行榜中的数据
		var games []*model.Game
		startIdx := (pn - 1) * limit
		err := dao.Game.Ctx(ctx).TX(tx).
			Fields(dao.Game.Columns).
			OrderDesc(dao.Game.Columns.UpdateTime).
			OrderAsc(dao.Game.Columns.Rank).
			Limit(startIdx, limit).
			Scan(&games)
		if err != nil {
			g.Log().Ctx(ctx).Errorf("查询数据库失败: %v", err)
			return gerror.New("获取排行榜失败")
		}
		if games == nil || len(games) == 0 {
			return gerror.New("获取排行榜失败, 请检查参数`pn`或`limit`")
		}
		resp.List = make([]*model.GameResp, len(games))
		for i, game := range games {
			resp.List[i], err = gs.getGameHelper(ctx, game)
			if err != nil {
				g.Log().Ctx(ctx).Errorf("查询数据库失败: %v", err)
				return gerror.New("获取排行榜失败")
			}
		}
		return nil
	})
	return resp, err
}

// Recommend 获取推荐游戏列表
func (gs *gameService) Recommend(ctx context.Context, uid uint) (resp []*model.GameResp, err error) {
	err = g.DB().Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error {
		var (
			games []*model.Game
			err   error
		)
		if uid == 0 { // 未登录用户, 随机推荐游戏
			games, err = dao.Game.GetRandomGames(ctx, 10)
		} else { // 已登录用户, 根据喜好推荐
			games, err = gs.recommendHelper(ctx, uid)
		}
		if err != nil {
			g.Log().Ctx(ctx).Errorf("查询数据库失败: %v", err)
			return gerror.New("获取推荐列表失败")
		}
		resp = make([]*model.GameResp, len(games))
		for i, game := range games {
			resp[i], err = gs.getGameHelper(ctx, game)
			if err != nil {
				g.Log().Ctx(ctx).Errorf("查询数据库失败: %v", err)
				return gerror.New("获取推荐列表失败")
			}
		}
		return nil
	})
	return resp, err
}

func (gs *gameService) recommendHelper(ctx context.Context, uid uint) (games []*model.Game, err error) {
	err = g.DB().Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error {
		var (
			recordsMap map[uint]uint
			key        = fmt.Sprintf("user:%d:category", uid)
		)
		vrecords, err := g.Redis().DoVar("HGETALL", key)
		if err != nil {
			// 查询redis发生错误, 直接使用随机推荐
			games, err = dao.Game.GetRandomGames(ctx, 10)
		}
		err = vrecords.Scan(&recordsMap)
		if err != nil || recordsMap == nil || len(recordsMap) < 3 {
			// 查询redis发生错误, 直接使用随机推荐
			games, err = dao.Game.GetRandomGames(ctx, 10)
		}
		records := sort.SortMapUintUint(recordsMap)[:3] // 取前三个最常访问的游戏种类
		var sumHints uint = 0                           // 游戏种类总访问量, 用于计算权重
		for _, record := range records {
			sumHints += record.Value
		}
		games = make([]*model.Game, 0, 10)
		for _, record := range records {
			var gss []*model.Game
			err = dao.Game.Ctx(ctx).TX(tx).Fields(dao.Game.Columns).
				WhereIn(dao.Game.Columns.Id,
					dao.GameCategory.Fields(dao.GameCategory.Columns.Gid).
						Where(dao.GameCategory.Columns.Cid, record.Key)).
				WhereNotIn(dao.Game.Columns.Id,
					dao.GameNotinterest.Fields(dao.GameNotinterest.Columns.Gid).
						Where(dao.GameNotinterest.Columns.Uid, uid)). // 不选择用户不感兴趣的游戏
				OrderRandom().
				Limit(int(10 * float64(record.Value) / float64(sumHints))). // 根据权重获取
				Scan(&gss)
			if err != nil {
				games, err = dao.Game.GetRandomGames(ctx, 10)
			}
			games = append(games, gss...)
		}
		if len(games) < 10 {
			var gss []*model.Game
			games, err = dao.Game.GetRandomGames(ctx, 10-len(games))
			if err != nil {
				return err
			}
			games = append(games, gss...)
		}
		return nil
	})
	return games, err
}

// Search 搜索游戏
func (gs *gameService) Search(ctx context.Context, key string, pn int, limit int) (
	resp *model.GameApiSearchResp, err error) {
	// 将key设置为搜索字段
	key = fmt.Sprintf("%%%v%%", key)
	err = g.DB().Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error {
		// 获取搜索器
		search := dao.Game.Ctx(ctx).TX(tx).
			WhereOrLike(dao.Game.Columns.Name, key). // 游戏名
			WhereOrLike(dao.Game.Columns.Description, key). // 游戏简介
			WhereOrIn(dao.Game.Columns.Id,
				dao.GameCategory.Fields(dao.GameCategory.Columns.Gid).
					WhereIn(dao.GameCategory.Columns.Cid,
						dao.Category.Fields(dao.Category.Columns.Id).
							WhereLike(dao.Category.Columns.Name, key))) // 游戏分类
		// 获取总数
		count, err := search.Count()
		if err != nil {
			g.Log().Ctx(ctx).Errorf("查询数据库发生错误: %v", err)
			return gerror.Newf("搜索失败")
		}
		// 构造响应
		resp = &model.GameApiSearchResp{
			Page: model.Page{
				Prev:  pn - 1,
				Limit: limit,
				Count: count,
			},
			List: make([]*model.GameResp, 0, limit),
		}
		if count == 0 {
			// 没有查询到游戏, 返回空列表
			resp.Prev = 0
			resp.Next = 0
			return nil
		}
		// 获取游戏
		var games []*model.Game
		err = search.Fields(dao.Game.Columns).Page(pn, limit).Scan(&games)
		if err != nil {
			g.Log().Ctx(ctx).Errorf("查询数据库发生错误: %v", err)
			return gerror.Newf("搜索失败")
		}
		if games == nil || len(games) == 0 {
			return gerror.Newf("查询失败, 请检查参数`pn`或`limit`")
		}
		// 分析下一页的页码
		if pn*limit >= count {
			resp.Next = 0
		} else {
			resp.Next = pn + 1
		}
		// 获取游戏
		resp.List = make([]*model.GameResp, len(games))
		for i, game := range games {
			resp.List[i], err = gs.getGameHelper(ctx, game)
			if err != nil {
				g.Log().Ctx(ctx).Errorf("查询数据库发生错误: %v", err)
				return gerror.Newf("搜索失败")
			}
		}
		return nil
	})
	return resp, err
}

// Similar 获取相似游戏
func (gs *gameService) Similar(ctx context.Context, id uint) (resp []*model.GameResp, err error) {
	err = g.DB().Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error {
		var games []*model.Game
		// 查询游戏
		err = dao.Game.Ctx(ctx).TX(tx).
			Fields(dao.Game.Columns).
			WhereIn(dao.Game.Columns.Id, dao.GameCategory.Fields(dao.GameCategory.Columns.Gid).
				WhereIn(dao.GameCategory.Columns.Cid,
					dao.GameCategory.Fields(dao.GameCategory.Columns.Cid).Where(dao.GameCategory.Columns.Gid, id))).
			OrderRandom().
			Limit(3).
			Scan(&games)
		if err != nil {
			g.Log().Ctx(ctx).Errorf("查询数据库发生错误: %v", err)
			return gerror.Newf("获取相似推荐失败")
		}
		resp = make([]*model.GameResp, len(games))
		for i, game := range games {
			resp[i], err = gs.getGameHelper(ctx, game)
			if err != nil {
				g.Log().Ctx(ctx).Errorf("查询数据库发生错误: %v", err)
				return gerror.Newf("获取相似推荐失败")
			}
		}
		return nil
	})
	return resp, err
}
